# This file is part of the AutoMAT distribution (https://bitbucket.com/mahomaho/AutoMAT).
# Copyright (c) 2020 Mattias Holmqvist.
# 
# AutoMAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# AutoMAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with AutoMAT.  If not, see <https://www.gnu.org/licenses/>.

import enum
import math
import re

class SimpleArrayElement(tuple):
	_multidim = True
	_text = True
	def __new__(cls, val = ()):
		if isinstance(val,str):
			val = eval(val)
		dims = []
		datatype = []
		v = cls._vals(val, dims, datatype)
		if not datatype:
			ret = tuple.__new__(cls)
			ret._dims = tuple()
			ret._datatype = None
		else:
			ret = tuple.__new__(cls, v)
			ret._dims = tuple(dims)
			ret._datatype = datatype[0]
		return ret
	@classmethod
	def _vals(cls, val, dims, datatype):
		subdims = []
		if isinstance(val, (int, float, str)):
			if isinstance(val, int):
				if datatype:
					if not datatype[0] in (int, float):
						raise ValueError('Mixed datatypes not accepted')
				else:
					datatype.append(int)
			elif isinstance(val, float):
				if datatype:
					if datatype[0] is int:
						datatype[0] = float
					elif not datatype[0] is float:
						raise ValueError('Mixed datatypes not accepted')
				else:
					datatype.append(float)
			elif isinstance(val, str):
				if not cls._text:
					raise ValueError('text content not allowed')
				if datatype:
					if not datatype[0] is  str:
						raise ValueError('Mixed datatypes not accepted')
				else:
					datatype.append(str)
			v = val
		else:
			try:
				v = tuple(cls._vals(element, subdims, datatype) for element in val)
			except TypeError as e:
				raise ValueError('Value cannot be converted')
			subdims.append(len(v))
		if dims:
			if not cls._multidim:
				raise ValueError('Multidim not allowed')
			if subdims != dims:
				raise ValueError('Dimensions not consistent')
		else:
			dims.extend(subdims)
		return v

class SimpleStringSelectionElement(str):
	__slots__=()
	def __new__(cls, val):
		return str.__new__(cls, val)
	def __init__(self,val):
		assert val in self._validStrings, f'"{val}" is not valid value for property of type {type(self).__name__}, does not fulfill regular expression ("{self._regex}")'
	@classmethod
	def valuetype(cls):
		return str
	@classmethod
	def values(cls):
		return cls._validStrings
	@classmethod
	def _validate(cls,val):
		assert val in cls._validStrings

class SimpleUnlimitedStringElement(str):
	__slots__=()
	def __new__(cls, val):
		assert isinstance(val,str)
		return str.__new__(cls, val)
	@classmethod
	def valuetype(cls):
		return str
	@classmethod
	def _validate(cls,val):
		assert isinstance(val,str)
	
class SimpleUnlimitedFloatElement(float):
	__slots__=()
	def __new__(cls, val):
		return float.__new__(cls, val)
	@classmethod
	def valuetype(cls):
		return float
	@classmethod
	def _validate(cls,val):
		float(val)
	def __repr__(self):
		if math.isnan(self):
			return 'NaN'
		return float.__repr__(self).upper()
	
class SimpleStringElement(str):
	__slots__=()
	def __new__(cls, val):
		assert isinstance(val,str)
		return str.__new__(cls, val)
	def __init__(self,val):
		#try:
		assert re.fullmatch(self._regex,val), f'"{val}" is not valid value for property of type {type(self).__name__}, does not fulfill regular expression ("{self._regex}")'
		#except:
		#	pass
	@classmethod
	def valuetype(cls):
		return str
	@classmethod
	def _validate(cls,val):
		assert re.fullmatch(cls._regex,val)

class SimpleIntegerElement(int):
	__slots__=()
	def __new__(cls, val):
		if isinstance(val, str):
			val=int(val,0)
		return int.__new__(cls, val)
	def __init__(self,val):
		if isinstance(val, str):
			assert re.fullmatch(self._regex,val), f'"{val}" is not valid value for property of type {type(self).__name__}, does not fulfill regular expression ("{self._regex}")'
	@classmethod
	def valuetype(cls):
		return int
	@classmethod
	def _validate(cls,val):
		int(val,0)

class SimpleFloatElement(float):
	__slots__=()
	def __new__(cls, val):
		if isinstance(val, str):
			val=float(val)
		return float.__new__(cls, val)
	def __init__(self,val):
		if isinstance(val, str):
			assert re.fullmatch(self._regex,val), f'"{val}" is not valid value for property of type {type(self).__name__}, does not fulfill regular expression ("{self._regex}")'
	def __repr__(self):
		if math.isnan(self):
			return 'NaN'
		return float.__repr__(self).upper()
	@classmethod
	def valuetype(cls):
		return float
	@classmethod
	def _validate(cls,val):
		float(val)

class SimpleNumericalElement(float):
	__slots__=('_valtype',)
	def __repr__(self):
		if self._valtype is float:
			if math.isnan(self):
				return 'NaN'
			return float.__repr__(self).upper()
		return int(self).__repr__()
	@property
	def __class__(self):
		if self._valtype is int:
			return SimpleIntegerElement
		return SimpleFloatElement
	def __new__(cls, val):
		if isinstance(val, str):
			assert re.fullmatch(cls._regex,val), f'"{val}" is not valid value for property of type {cls.__name__}, does not fulfill regular expression ("{cls._regex}")'		
			val=eval(val,{'true':True,'TRUE':True,'false':False,'FALSE':False},{})
		if isinstance(val,int):
			valtype=int
		elif isinstance(val,float):
			valtype=float
		else:
			raise ValueError('Not a valid numerical value')
		self=float.__new__(cls,val)
		self._valtype=valtype
		return self
	def valuetype(self):
		return self._valtype
	@classmethod
	def _validate(cls,val):
		assert re.fullmatch(cls._regex,val)
	

class SimpleBoolElement(int):
	__slots__=()
	def __new__(cls, val):
		if isinstance(val, str):
			val=val.lower()
			if val in ('1','true'):
				val=1
			else:
				val=0
		else:
			assert val>=0 and val<=1, 'Invalid boolean value'
		return int.__new__(cls, val)
	def __init__(self,val):
		if isinstance(val, str):
			val=val.lower()
			assert re.fullmatch(self._regex,val), f'"{val}" is not valid value for property of type {type(self).__name__}, does not fulfill regular expression ("{self._regex}")'
	def __str__(self):
		if self==0:
			return 'false'
		return 'true'
	def __repr__(self):
		if self==0:
			return 'False'
		return 'True'
	@classmethod
	def values(cls):
		return (True,False)
	@classmethod
	def valuetype(cls):
		return bool
	@classmethod
	def _validate(cls,val):
		assert val in ('true','TRUE','false','FALSE')

class Enum(enum.Enum):
	__slots__=()
	def __new__(cls, value, doc=None):
		self = object.__new__(cls)  # calling super().__new__(value) here would fail
		self._value_ = value
		if doc is not None:
			self.__doc__ = doc
		return self
	@classmethod
	def valuetype(cls):
		return cls
	@classmethod
	def values(cls):
		return list(cls.__members__.keys())
	@classmethod
	def _missing_(cls, value):
		return cls.__members__[value]
	def __str__(self):
		return self.name
	def __repr__(self):
		return enum.Enum.__str__(self)
	@classmethod
	def _validate(cls,val):
		assert val in (e.value for e in cls.__members__.values())
